/* 
Copyright 2011 by Mark Coletti, Keith Sullivan, Sean Luke, and
George Mason University Mason University Licensed under the Academic
Free License version 3.0

See the file "LICENSE" for more information
*/
/*
 * Network.java
 *
 * $Id: GeomPlanarGraph.java,v 1.3 2010-08-25 20:05:25 mcoletti Exp $
 */
package sim.util.geo;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateArrays;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.planargraph.DirectedEdge;
import com.vividsolutions.jts.planargraph.Edge;
import com.vividsolutions.jts.planargraph.Node;
import com.vividsolutions.jts.planargraph.PlanarGraph;
import sim.field.geo.GeomVectorField;
import sim.field.network.Network;
import sim.util.Bag;

/** A JTS PlanarGraph
 *
 * Planar graph useful for exploiting network topology.
 *
 * @see sim.app.geo.networkworld and sim.app.geo.campusworld
 *
 */
public class GeomPlanarGraph extends PlanarGraph
{

    public GeomPlanarGraph()
    {
        super();
    }

    /** populate network with lines from a GeomVectorField
     *
     * @param field containing line segments
     *
     * Assumes that 'field' contains co-planar linear objects
     *
     */
    public void createFromGeomField(GeomVectorField field)
    {
        Bag geometries = field.getGeometries();

        for (int i = 0; i < geometries.numObjs; i++)
        {
            if (((MasonGeometry) geometries.get(i)).geometry instanceof LineString)
            {
                LineString lineString = (LineString) ((MasonGeometry) geometries.get(i)).geometry;

                addLineString(lineString);
            }
        }

        // Abandoned work on rectifying non-planar data. MAC 8/24/10.
//        field.clear();
//
//        Collection<LineString> lines = lineMerger.getMergedLineStrings();
//
//        PrecisionModel pm = new PrecisionModel(PrecisionModel.FLOATING);
//
//        GeometryNoder noder = new GeometryNoder(pm);
//
//        Collection<LineString> more_lines = noder.node(lines);
//
//        // Now add nodes and edges to graph
//
//        for (LineString line : more_lines)
//        {
//            addLineString(line);
//            field.addGeometry(new MasonGeometry(line));
        //}

    }

    /** Add the given line to the graph
     * 
     * @param line
     *
     * @note Some code copied from JTS PolygonizeGraph.addEdge() and hacked
     * to fit
     */
    private void addLineString(LineString line)
    {
        if (line.isEmpty())
        {
            return;
        }

        Coordinate[] linePts = CoordinateArrays.removeRepeatedPoints(line.getCoordinates());

        if (linePts.length < 2)
        {
            return;
        }

        Coordinate startPt = linePts[0];
        Coordinate endPt = linePts[linePts.length - 1];

        Node nStart = getNode(startPt); // nodes added as necessary side-effect
        Node nEnd = getNode(endPt);

        Edge edge = new GeomPlanarGraphEdge(line);

        GeomPlanarGraphDirectedEdge de0 = new GeomPlanarGraphDirectedEdge(nStart, nEnd, linePts[1], true);
        GeomPlanarGraphDirectedEdge de1 = new GeomPlanarGraphDirectedEdge(nEnd, nStart, linePts[linePts.length - 2], false);

        edge.setDirectedEdges(de0, de1);

        add(edge);
    }

    /** get the node corresponding to the coordinate
     *
     * @param startPt
     * @return graph node associated with point
     *
     * Will create a new Node if one does not exist.
     *
     * @note Some code copied from JTS PolygonizeGraph.getNode() and hacked to fit
     */
    private Node getNode(Coordinate pt)
    {
        Node node = findNode(pt);
        if (node == null)
        {
            node = new Node(pt);
            // ensure node is only added once to graph
            add(node);
        }
        return node;
    }

    /** Create a MASON Network from this planar graph
     *
     * XXX Unfortunately we need this since JTS planar graphs do not support
     * shortest distance and other common graph traversals.
     */
    public Network getNetwork()
    {
        Network network = new Network(false); // false == not directed

        for ( Object object : getEdges() )
        {
            DirectedEdge edge = (DirectedEdge) object;

            network.addEdge(edge.getFromNode(), edge.getToNode(), edge);
        }

        return network;
    }

}
